/************************************************************************/
/*                                                                      */
/*  Typecast Project                                                    */
/*                                                                      */
/*  User mode client to test kernel mode netlink socket server          */
/*  Author: Eusden Shing, UCLA                                          */
/*                                                                      */
/************************************************************************/
/*
#include <linux/types.h>
#include <sys/socket.h>
#include <socket.h>
#include <linux/netlink.h>
#include <string.h>
*/

#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<asm/types.h>
#include<linux/netlink.h>
#include<linux/socket.h>

#include "typecast-rt.h"

#define MAX_PAYLOAD 1024

struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
struct msghdr msg;

int sock_fd;

void dumpRoutingTable()
{
    char srcAddr[16], dstAddr[16];
    int RoutingTableCount, i;
    RoutingTableEntry rteTemp;
    RoutingTableEntry* RoutingTable;


    printf("dumpRoutingTable - enter.\n");


    memset(srcAddr, 0, 16);
    memset(dstAddr, 0, 16);

    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TYPECAST_DUMP_RT);

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;      
    src_addr.nl_pid = getpid();
    src_addr.nl_groups = 0;
    bind(sock_fd, (struct sockaddr*) &src_addr, sizeof(src_addr));

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;
    dest_addr.nl_groups = 0;


    nlh = (struct nlmsghdr *) malloc( NLMSG_SPACE(MAX_PAYLOAD) );

    /* Fill the netlink message header */
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();  /* self pid */
    nlh->nlmsg_flags = 0;

    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;

    memset(&msg,0,sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // Send the message into kernel space 
    sendmsg(sock_fd, &msg, 0);

    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    // Now wait for the kernel's response
    recvmsg(sock_fd, &msg, 0);


    RoutingTableCount = 0;
    do
    {
        memcpy( &rteTemp, NLMSG_DATA(nlh) + (sizeof(RoutingTableEntry) * RoutingTableCount), sizeof(RoutingTableEntry) );
        RoutingTableCount++;
    } while (rteTemp.srcAddr != 0);

    RoutingTable = malloc( sizeof(RoutingTableEntry) * RoutingTableCount);

    for (i = 0; i < RoutingTableCount; i++)
    {
        memcpy( &(RoutingTable[i]), NLMSG_DATA(nlh) + (sizeof(RoutingTableEntry) * i), sizeof(RoutingTableEntry) );
    }
    
    // TODO PROCESSING

    printf("srcAddr = %s, dstAddr = %s, BloomFilter(byte) = %s.\n", 
        inet_ntoa(RoutingTable[0].srcAddr), inet_ntoa(RoutingTable[0].dstAddr), RoutingTable[0].BloomFilter);

    close(sock_fd);
    printf("dumpRoutingTable - exit.\n");
}

void setRoutingTable()
{
    RoutingTableEntry* RoutingTable = NULL;
    int i = 0, j = 0, iRoutingTableEntriesCount = 0, RoutingTableBytes = 0;
    FILE * pFile = NULL;
    char srcAddr[16], dstAddr[16];
    char tempBloomFilter[BLOOMFILTERSIZE + 1];    // BloomFilter in CHAR form

    // Clear the memory
    memset(srcAddr, 0, 16);
    memset(dstAddr, 0, 16);
    memset(tempBloomFilter, 0, BLOOMFILTERSIZE + 1);


    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TYPECAST_SET_RT);

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;      
    src_addr.nl_pid = getpid();
    src_addr.nl_groups = 0;
    bind(sock_fd, (struct sockaddr*) &src_addr, sizeof(src_addr));

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;
    dest_addr.nl_groups = 0;


    /* This RoutingTable.txt will have the following format:
    5                                         | number of entries (including local BloomFilter entry, and terminating entry)
    255.255.255.255 255.255.255.255 AAAAAAAA  | srcAddr, dstAddr, BloomFilter.  Local BloomFilter when srcAddr = dstAddr = 1.
    177.64.3.12     224.0.0.1       BBBBBBBB  |    srcAddr and dstAddr are 32 bit unsigned integer
    23.44.160.24    224.0.0.2       CCCCCCCC  |    BloomFilter has BLOOMFILTERSIZE/2 characters.
    22.160.12.55    224.0.0.3       DDDDDDDD  |
    0.0.0.0         0.0.0.0         00000000  | (This is the terminating, with all data as 0.)
    */

    pFile = fopen("RoutingTable.txt", "r");
    if (pFile == NULL)
    {
        printf("ERROR: RoutingTable.txt not found.  Exiting.\n");
        return;
    }

    // Read number of entries
    fscanf(pFile, "%d", &iRoutingTableEntriesCount);

    // Allocate enough memory to hold all the entries
    RoutingTableBytes = sizeof(RoutingTableEntry) * iRoutingTableEntriesCount;
    RoutingTable = malloc( RoutingTableBytes );
    memset( RoutingTable, 0, RoutingTableBytes );

    // Read in all the entries from the file
    for (i = 0; i < iRoutingTableEntriesCount; i++)
    {
        fscanf(pFile, "%s %s %s", srcAddr, dstAddr, tempBloomFilter);

        RoutingTable[i].srcAddr = inet_addr(srcAddr);
        RoutingTable[i].dstAddr = inet_addr(dstAddr);

        // Convert BloomFilter from char-array format to byte-format.
        //   (tempBloomFilter -> RoutingTable[i].BloomFilter)
        for (j = 0; j < BLOOMFILTERSIZE; j++)
        {
            int tempInt;
            if (('0' <= tempBloomFilter[j]) && (tempBloomFilter[j] <= '9'))
                tempInt = tempBloomFilter[j] - '0';
            else if (('A' <= tempBloomFilter[j]) && (tempBloomFilter[j] <= 'F'))
                tempInt = tempBloomFilter[j] - 'A' + 0xA;
            else if (('a' <= tempBloomFilter[j]) && (tempBloomFilter[j] <= 'f'))
                tempInt = tempBloomFilter[j] - 'a' + 0xA;
            else
                assert(0);

            printf("j=%d, tempInt=%x\n", j, tempInt);

            if ((j % 2) == 0)
                RoutingTable[i].BloomFilter[j/2] = (tempInt << 4);
            else
                RoutingTable[i].BloomFilter[j/2] += (tempInt);
        }

        printf("srcAddr = %s, dstAddr = %s, BloomFilter(char) = %s, BloomFilter(byte) = %s.\n", 
            inet_ntoa(RoutingTable[i].srcAddr), inet_ntoa(RoutingTable[i].dstAddr), tempBloomFilter, RoutingTable[i].BloomFilter);
    }

    nlh = (struct nlmsghdr *) malloc( NLMSG_SPACE(MAX_PAYLOAD) );

    /* Fill the netlink message header */
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();  /* self pid */
    nlh->nlmsg_flags = 0;

    /* Fill in the netlink message payload */
    memcpy(NLMSG_DATA(nlh), RoutingTable, RoutingTableBytes);

    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;

    memset(&msg,0,sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // Send the message into kernel space 
    sendmsg(sock_fd, &msg, 0);

    close(sock_fd);

}

main(int argc, char **argv)
{
    if (argc < 2)
        printf("Invalid parameter, either 'dump' or 'set'.\n");
    else
    {
        if (strcmp(argv[1], "dump") == 0)
        {
            dumpRoutingTable();
        }
        else if (strcmp(argv[1], "set") == 0)
        {
            setRoutingTable();
        }
        else
            printf("Invalid parameter, either 'dump' or 'set'.\n");
    }
}